/* Python plug-in for dia
 * Copyright (C) 1999  James Henstridge
 * Copyright (C) 2000  Hans Breuer
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <config.h>


#include "pydia-object.h"
#include "pydia-diagramdata.h"

#include "pydia-geometry.h"
#include "pydia-layer.h"
#include "pydia-color.h"
#include "pydia-paperinfo.h"

#include "app/diagram.h"

PyObject *
PyDiaDiagramData_New(DiagramData *dd)
{
    PyDiaDiagramData *self;

    self = PyObject_NEW(PyDiaDiagramData, &PyDiaDiagramData_Type);

    if (!self) return NULL;
    g_object_ref (dd);
    self->data = dd;
    return (PyObject *)self;
}

static void
PyDiaDiagramData_Dealloc(PyDiaDiagramData *self)
{
    g_object_unref (self->data);
    PyObject_DEL(self);
}

static int
PyDiaDiagramData_Compare(PyDiaDiagramData *self, PyDiaDiagramData *other)
{
    if (self->data == other->data) return 0;
    if (self->data > other->data) return -1;
    return 1;
}

static long
PyDiaDiagramData_Hash(PyDiaDiagramData *self)
{
    return (long)self->data;
}

static PyObject *
PyDiaDiagramData_Str(PyDiaDiagramData *self)
{
    PyObject* py_s;
    gchar* s = g_strdup_printf ("<PyDiaDiagramData %p>", self);
    py_s = PyString_FromString(s);
    g_free(s);
    return py_s;
}

/*
 * "real" member function implementaion ?
 */
static PyObject *
PyDiaDiagramData_UpdateExtents(PyDiaDiagramData *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ":DiagramData.update_extents"))
	return NULL;
    data_update_extents(self->data);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
PyDiaDiagramData_GetSortedSelected(PyDiaDiagramData *self, PyObject *args)
{
    GList *list, *tmp;
    PyObject *ret;
    guint i, len;

    if (!PyArg_ParseTuple(args, ":DiagramData.get_sorted_selected"))
	return NULL;
    list = tmp = data_get_sorted_selected(self->data);

    len = g_list_length (self->data->selected);
    ret = PyTuple_New(len);

    for (i = 0, tmp = self->data->selected; tmp; i++, tmp = tmp->next)
	PyTuple_SetItem(ret, i, PyDiaObject_New((DiaObject *)tmp->data));
    g_list_free(list);
    return ret;
}

static PyObject *
PyDiaDiagramData_AddLayer(PyDiaDiagramData *self, PyObject *args)
{
    gchar *name;
    int pos = -1;
    Layer *layer;

    if (!PyArg_ParseTuple(args, "s|i:DiagramData.add_layer", &name, &pos))
	return NULL;
    layer = new_layer(g_strdup(name),self->data);
    if (pos != -1)
	data_add_layer_at(self->data, layer, pos);
    else
	data_add_layer(self->data, layer);
    return PyDiaLayer_New(layer);
}

static PyObject *
PyDiaDiagramData_RaiseLayer(PyDiaDiagramData *self, PyObject *args)
{
    PyDiaLayer *layer;

    if (!PyArg_ParseTuple(args, "O!:DiagramData.raise_layer",
			  &PyDiaLayer_Type, &layer))
	return NULL;
    data_raise_layer(self->data, layer->layer);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
PyDiaDiagramData_LowerLayer(PyDiaDiagramData *self, PyObject *args)
{
    PyDiaLayer *layer;

    if (!PyArg_ParseTuple(args, "O!:DiagramData.lower_layer",
			  &PyDiaLayer_Type, &layer))
	return NULL;
    data_lower_layer(self->data, layer->layer);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
PyDiaDiagramData_SetActiveLayer(PyDiaDiagramData *self, PyObject *args)
{
    PyDiaLayer *layer;

    if (!PyArg_ParseTuple(args, "O!:DiagramData.set_active_layer",
			  &PyDiaLayer_Type, &layer))
	return NULL;
    data_set_active_layer(self->data, layer->layer);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
PyDiaDiagramData_DeleteLayer(PyDiaDiagramData *self, PyObject *args)
{
    PyDiaLayer *layer;

    if (!PyArg_ParseTuple(args, "O!:DiagramData.delete_layer",
			  &PyDiaLayer_Type, &layer))
	return NULL;
    data_delete_layer(self->data, layer->layer);
    layer_destroy(layer->layer);
    layer->layer = NULL;
    Py_INCREF(Py_None);
    return Py_None;
}

/* 
 *  Callback for "object_add" and "object_remove "signal, used by the connect_after method,
 *  it's a proxy for the python function, creating the values it needs.
 *  Params are those of the signals on the Diagram object.
 *  @param dia The DiagramData that emitted the signal
 *  @param layer The Layer that the object is removed or added to.
 *  @param obj The DiaObject that the signal is about.
 *  @param user_data The python function to be called by the callback.
 */
static void
PyDiaDiagramData_CallbackObject(DiagramData *dia,Layer *layer,DiaObject *obj,void *user_data)
{
    PyObject *pydata,*pylayer,*pyobj,*res,*arg;
    PyObject *func = user_data;
    
    /* Check that we got a function */   
    if (!func || !PyCallable_Check (func)) {
        g_warning ("Callback called without valid callback function.");
        return;
    }
      
    /* Create a new PyDiaDiagramData object.
     */
    if (dia)
        pydata = PyDiaDiagramData_New (dia);
    else {
        pydata = Py_None;
        Py_INCREF (pydata);
    }
      
    /*
     * Create PyDiaLayer
     */
    if (layer)
        pylayer = PyDiaLayer_New (layer);
    else {
        pylayer = Py_None;
        Py_INCREF (pylayer);
    }   
    
    /*
     * Create PyDiaObject
     */
    if (layer)
        pyobj = PyDiaObject_New (obj);
    else {
        pyobj = Py_None;
        Py_INCREF (pyobj);
    }   
    
    
    Py_INCREF(func);

    /* Call the callback. */
    arg = Py_BuildValue ("(OOO)", pydata,pylayer,pyobj);
    if (arg) {
      res = PyEval_CallObject (func, arg);
      /*ON_RES(res, TRUE);*/
    }
    
    /* Cleanup */
    Py_XDECREF (arg);
    Py_DECREF(func);
    Py_XDECREF(pydata);
    Py_XDECREF(pylayer);
    Py_XDECREF(pyobj);
}


/** Connects a python function to a signal.
 *  @param self The PyDiaDiagramData this is a method of.
 *  @param args A tuple containing the arguments, a str for signal name
 *  and a callable object (like a function)
 */
static PyObject *
PyDiaDiagramData_ConnectAfter(PyDiaDiagramData *self, PyObject *args)
{
    PyObject *func;
    char *signal;

    /* Check arguments */
    if (!PyArg_ParseTuple(args, "sO:DiagramData.connect_after",&signal,&func))
        return NULL;

    /* Check that the arg is callable */
    if (!PyCallable_Check(func)) {
        PyErr_SetString(PyExc_TypeError, "Second parameter must be callable");
        return NULL;
    }

    /* check if the signals name is valid */
    if ( strcmp("object_remove",signal) == 0 || strcmp("object_add",signal) == 0) {

        Py_INCREF(func); /* stay alive, where to kill ?? */

        /* connect to signal */
        g_signal_connect_after(DIA_DIAGRAM_DATA(self->data),signal,G_CALLBACK(PyDiaDiagramData_CallbackObject), func);
        
        Py_INCREF(Py_None);
        return Py_None;
    }
    else {
            PyErr_SetString(PyExc_TypeError, "Wrong signal name");
            return NULL;
    }
}


static PyMethodDef PyDiaDiagramData_Methods[] = {
    {"update_extents", (PyCFunction)PyDiaDiagramData_UpdateExtents, 1},
    {"get_sorted_selected", (PyCFunction)PyDiaDiagramData_GetSortedSelected, 1},
    {"add_layer", (PyCFunction)PyDiaDiagramData_AddLayer, 1},
    {"raise_layer", (PyCFunction)PyDiaDiagramData_RaiseLayer, 1},
    {"lower_layer", (PyCFunction)PyDiaDiagramData_LowerLayer, 1},
    {"set_active_layer", (PyCFunction)PyDiaDiagramData_SetActiveLayer, 1},
    {"delete_layer", (PyCFunction)PyDiaDiagramData_DeleteLayer, 1},
    {"connect_after", (PyCFunction)PyDiaDiagramData_ConnectAfter, 1},
    {NULL, 0, 0, NULL}
};

static PyObject *
PyDiaDiagramData_GetAttr(PyDiaDiagramData *self, gchar *attr)
{
    Diagram *diagram = DIA_DIAGRAM(self->data);

    if (!strcmp(attr, "__members__"))
	return Py_BuildValue("[ssssssssssss]",
                           "extents", "bg_color", "paper",
                           "layers", "active_layer", 
                           "grid_width", "grid_visible", 
                           "hguides", "vguides",
                           "layers", "active_layer",
                           "selected" );
    else if (!strcmp(attr, "extents"))
      return PyDiaRectangle_New(&self->data->extents, NULL);
    else if (!strcmp(attr, "bg_color")) {
      return PyDiaColor_New (&(self->data->bg_color));
    }
    else if (!strcmp(attr, "layers")) {
	guint i, len = self->data->layers->len;
	PyObject *ret = PyTuple_New(len);

	for (i = 0; i < len; i++)
	    PyTuple_SetItem(ret, i, PyDiaLayer_New(
			g_ptr_array_index(self->data->layers, i)));
	return ret;
    } else if (!strcmp(attr, "active_layer")) {
	return PyDiaLayer_New(self->data->active_layer);
    } else if (!strcmp(attr, "paper")) {
        return PyDiaPaperinfo_New (&self->data->paper);
    }
    else if (diagram && !strcmp(attr, "grid_width")) 
      return Py_BuildValue("(dd)", diagram->grid.width_x, diagram->grid.width_y);
    else if (diagram && !strcmp(attr, "grid_visible")) 
      return Py_BuildValue("(ii)", diagram->grid.visible_x, diagram->grid.visible_y);
    else if (diagram && !strcmp(attr, "hguides")) {
      int len = diagram->guides.nhguides;
      PyObject *ret = PyTuple_New(len);
      int i;
      for (i = 0; i < len; i++)
        PyTuple_SetItem(ret, i, PyFloat_FromDouble(diagram->guides.hguides[i]));
      return ret;
    }
    else if (diagram && !strcmp(attr, "vguides")) {
      int len = diagram->guides.nvguides;
      PyObject *ret = PyTuple_New(len);
      int i;
      for (i = 0; i < len; i++)
        PyTuple_SetItem(ret, i, PyFloat_FromDouble(diagram->guides.vguides[i]));
      return ret;
    }
    else if (!strcmp(attr, "layers")) {
	guint i, len = self->data->layers->len;
	PyObject *ret = PyTuple_New(len);

	for (i = 0; i < len; i++)
	    PyTuple_SetItem(ret, i, PyDiaLayer_New(
			g_ptr_array_index(self->data->layers, i)));
	return ret;
    }
    else if (!strcmp(attr, "active_layer")) {
      return PyDiaLayer_New(self->data->active_layer);
    }
    else if (!strcmp(attr, "selected")) {
	PyObject *ret;
	GList *tmp;
	gint i;

	ret = PyTuple_New(g_list_length(self->data->selected));
	for (i = 0, tmp = self->data->selected; tmp; i++, tmp = tmp->next)
	    PyTuple_SetItem(ret, i, PyDiaObject_New((DiaObject *)tmp->data));
	return ret;
    }

    return Py_FindMethod(PyDiaDiagramData_Methods, (PyObject *)self, attr);
}

PyTypeObject PyDiaDiagramData_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "dia.DiagramData",
    sizeof(PyDiaDiagramData),
    0,
    (destructor)PyDiaDiagramData_Dealloc,
    (printfunc)0,
    (getattrfunc)PyDiaDiagramData_GetAttr,
    (setattrfunc)0,
    (cmpfunc)PyDiaDiagramData_Compare,
    (reprfunc)0,
    0,
    0,
    0,
    (hashfunc)PyDiaDiagramData_Hash,
    (ternaryfunc)0,
    (reprfunc)PyDiaDiagramData_Str,
    (getattrofunc)0,
    (setattrofunc)0,
    (PyBufferProcs *)0,
    0L, /* Flags */
    "The 'low level' diagram object. It contains everything to manipulate diagrams from im- and export "
    "filters as well as from the UI. It does not provide any access to GUI elements related to the diagram."
    "Use the subclass dia.Diagram object for such matters."
};
